iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0
Modern Web

開始搞懂React生態系系列 第 13

Day 13 React.forwardRef (HOC)、useImperativeHandle

  • 分享至 

  • xImage
  •  

要介紹 forwardRef 之前,我們要先知道 HOC 這個 React 特有的設計概念。

Higher-Order Component (HOC)

Higher-Order Component 是一種設計概念:「把一個 Component 傳進去給 HOC Function,它會幫你加工成為一個新的有額外功能或資料的增強版 Component」。

const EnhancedComponent = aHOCFunction(WrappedComponent);

forwardRef

forwardRef 會傳入一個 Function Component,此 FC 除了傳入 props,也會傳入 ref 物件參考,再將它們傳到 JSX 中。當 ref 物件傳進內部後,就可以標記任意的 DOM 元素。

forwardRef:傳送 ref 物件到 Function Component 內部

語法如下:

const InputField = React.forwardRef((props, ref) => {
  return (
    <input type="text" ref={ref} />
  );
});
const DivItem = React.forwardRef((props, ref) => {
  return (
    <div ref={ref}>{props.children}</div>
  );
});

使用上面用 forwardRef 加工完成的 Enhanced Component,取得參考後,再對其做 DOM 操作

程式碼簡單示意如下

function App() {
  const inputRef = React.useRef();
  const divRef = React.useRef();
  ...
  // inputRef.current.focus
  // divRef.current.style.backgroundColor = '#FF0000'
  return (
    <div>
      <InputField ref={inputRef} />
      <DivItem ref={divRef} />
    </div>
  )
}

完整程式碼執行結果:https://codepen.io/lala-lee-jobs/pen/GRdNvgz

useImperativeHandle

理解完 forwardRef,就可以再繼續研究 useImperativeHandle

  • useImperativeHandle 可以定義出「未來父元件要使用 ref 時可使用的操作方法」
  • useImperativeHandle 都會與 forwardRef 一同使用

使用 useImperativeHandle 的步驟

Step 1. 先製作好要傳入 forwardRef 的 Function Component

const CustomComponentFn = (props, ref) => {...}

Step 2. 定義好實際要對應原生 HTML DOM 元素的參考

const CustomComponentFn = (props, ref) => {
  const domRef = React.useRef();
}

Step 3. 要回傳的 jsx 綁定好 Step 2 中設定的參考

const CustomComponentFn = (props, ref) => {
  const domRef = React.useRef();
  return <div ref={domRef} />
}

Step 4. 使用 useImperativeHandle 定義未來使用此元件的 ref 時可以操作的方法

const CustomComponentFn = (props, ref) => {
  const domRef = React.useRef();
  React.useImperativeHandle(ref, () => ({
    doSomething: () => {
      // domRef.current.focus();
      // domRef.current.value = ....
      // domRef.current.style = ....
    }
  }));
  return <div ref={domRef} />
}

Step 5. 使用 fowardRef 包裝好要使用的 Enhanced Component

const CustomComponentFn = (props, ref) => {...}
const CustomComponent = React.forwardRef(CustomComponentFn);

Step 6. 在父元件中使用 Enhanced Component 的 ref 時就可以直接用剛剛在 useImperativeHandle 定義的操作方法

const CustomComponentFn = (props, ref) => {...}
const CustomComponent = React.forwardRef(CustomComponentFn);
const App = () => {
  const ccRef = React.useRef();
  // 之後就可以直接 ccRef.current.doSomething();
  return <CustomComponent ref={ccRef} />
}                                           

useImperativeHandle 實際範例

我們可以延續上面的 forwardRef 範例,改良成 useImperativeHandle 的用法

  • 先使用 useImperativeHandle 及 forwardRef 做出 Enhanced Component
const InputFieldFn = (props, ref) => {
  const inputRef = React.useRef();
  // 使用 useImperativeHandle 定義父元件使用 ref 時可使用的操作方法
  React.useImperativeHandle(ref, () => ({
    focusAndHightlight: () => {
      inputRef.current.focus();
      // 設定游標顏色
      inputRef.current.style.caretColor = 'red';
    }
  }));
  return (
    <div>
      <h2>InputField</h2>
      <input ref={inputRef} />
    </div>
   );
}
const InputField = React.forwardRef(InputFieldFn);
const DivItemFn = (props, ref) => {
  const divRef = React.useRef();
  // 使用 useImperativeHandle 定義父元件使用 ref 時可使用的操作方法
  React.useImperativeHandle(ref, () => ({
    changeColor: () => {
      const randomColor = Math.floor(Math.random()*16777215).toString(16);
      divRef.current.style.backgroundColor = "#" + randomColor;
    }
  }));  
  return (
    <div>
      <h2>DivItem</h2>
      <div ref={divRef}>{props.children}</div>
    </div>
  );
}

const DivItem = React.forwardRef(DivItemFn);
  • 在父元件中使用 Enhanced Component 的 ref 時就可以直接用剛剛在 useImperativeHandle 定義的操作方法
const App = () => {
  const inputFieldRef = React.useRef();
  const divItemRef = React.useRef();
  // inputFieldRef.current.focusAndHightlight();
  // divItemRef.current.changeColor();
  return (
    <div>
      <InputField ref={inputFieldRef} />
      <DivItem ref={divItemRef}>This is the content</DivItem>
    </div>
  )
}

完整程式碼執行結果:https://codepen.io/lala-lee-jobs/pen/VwxmXpz

fowardRef 及 useImperativeHandle 光看說明比較不好理解,跟著範例實際操作後,就會比較清楚用法,請耐心跟著做看看吧!

Next

下一章要介紹的是可以簡單使用又實用的功能性 Hook - useIduseLayoutEffect

Reference

https://pjchender.dev/react/react-higher-order-component/

https://medium.com/@zongrong.h886/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-react-useref-hook-1efedcaef26d

https://zh-hant.reactjs.org/docs/hooks-reference.html#useimperativehandle


上一篇
Day 12 useRef
下一篇
Day 14 useId、useLayoutEffect
系列文
開始搞懂React生態系30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言